//
//  Board.m
//  Mockup
//
//  Created by JOHN HODGSON on 3/2/10.
//  Copyright 2010 Mac-Easy@USC. All rights reserved.
//

#import "Board.h"
#import "Sound.h"

@implementation Board

@synthesize state;
@synthesize score;
@synthesize time;

//Testing for delaying code
//BOOL readyThing = FALSE;

//This method should initialize a new game board with the random matrix
-(id)initWithRandomMatrix:(int)difficulty{
	self.state = Loading;
	[self findRandomMatrix:difficulty];
	[self loadTiles: matrix];
	//TEST MODIFY SCORE!!!
	score = 0;
	currentDirection = None;
	[self setDifficultyLevel:difficulty];
	[self buildScoreMatrix];
	
	return self;
}

-(id) initWithContentOfFile:(NSString *)filename {
	if (self = [super init]) {
		self.state = Loading;
		[self loadGameFromFile:filename];
		[self loadTiles: matrix];
		currentDirection = None;
		
	}
	return self;
}

//-(void)buildMatrix:(int)diffNum {
//	//initializes the random array
//	for (int i = 0; i<ARRAYHEIGHT; i++){
//		for (int j = 0; j<ARRAYWIDTH; j++){
//			matrix[j][i] = arc4random() % diffNum;
//		}
//	}
//}

-(void)buildMatrix:(int)diffNum 
{
	//initializes the random array
	int tempVal;
	for (int i = 0; i<ARRAYHEIGHT; i++)
	{
		for (int j = 0; j<ARRAYWIDTH; j++)
		{
			if(i < 2 && j < 2)
			{
				matrix[j][i] = arc4random() % diffNum;
			}
			else if ((matrix[j][i-1] != matrix[j][i-2]) && (matrix[j-1][i] != matrix[j-2][i]))
			{
				matrix[j][i] = arc4random() % diffNum;
			}
			else
			{
				tempVal = arc4random() % diffNum;
				if((matrix[j][i-1] == matrix[j][i-2]) && (matrix[j-1][i] != matrix[j-2][i]))
				{
					while(tempVal==matrix[j][i-1])
					{
						tempVal = arc4random() % diffNum;
					}

					matrix[j][i] = tempVal;
				}
				else if((matrix[j][i-1] != matrix[j][i-2]) && (matrix[j-1][i] == matrix[j-2][i]))
				{
					while(tempVal==matrix[j-1][i])
					{
						tempVal = arc4random() % diffNum;
					}
					
					matrix[j][i] = tempVal;
				}
				else
				{
					while(tempVal==matrix[j][i-1])
					{
						tempVal = arc4random() % diffNum;
					}

					matrix[j][i] = tempVal;
				}
				
			}
		}
	}
}

-(void)buildZeroesMatrix{
	for (int i = 0; i<ARRAYHEIGHT; i++){
		for (int j = 0; j<ARRAYWIDTH; j++){
			matchMatrix[j][i] = 0;
		}
	}
}

-(void)buildScoreMatrix{
	for(int i = 0; i<SCOREWIDTH; i++){
		scoreMatrix[i][0] = i+3;
		scoreMatrix[i][1] = 0;
	}
}

-(BOOL)checkForMatches:(int[ARRAYWIDTH][ARRAYHEIGHT])checkMatrix{
	
	printf("In the checkForMatches method...\n");
	
	//printf("This is the matrix to be checked:\n");
	//[Board printMatrix:checkMatrix];
	
	//HORIZONTAL CHECK
	//printf("Horizontal check...");
	for (int i = 0; i<ARRAYWIDTH; i++) {  
		for (int j = 2; j<ARRAYHEIGHT; j++) {  
			if(checkMatrix[i][j] != Empty){
				if(checkMatrix[i][j] == checkMatrix[i][j-1]){
					if(checkMatrix[i][j] == checkMatrix[i][j-2]){
						//printf("Match found starting at %d, %d, sequence: %d, %d, %d. \n", i, j-2, 
						//	   checkMatrix[i][j-2] ,checkMatrix[i][j-1], checkMatrix[i][j]);
						//[self buildMatchMatrix:checkMatrix];
						return TRUE;
					}
				}
			}
		}  
	}
	//VERTICAL CHECK
	//printf("Vertical check...");
	for (int i = 0; i< ARRAYHEIGHT; i++) {  
		for (int j = 2; j<ARRAYWIDTH; j++) {  
			if(checkMatrix[j][i] != Empty){
				if(checkMatrix[j][i] == checkMatrix[j-1][i]){
					if(checkMatrix[j][i] == checkMatrix[j-2][i]){
						//printf("Match found starting at %d, %d, sequence: %d, %d, %d. \n", j-2, i, 
						//	   checkMatrix[j-2][i] ,checkMatrix[j-1][i], checkMatrix[j][i]);
						//[self buildMatchMatrix:checkMatrix];
						return TRUE;
					}
				}		
				
			}
		}  
	} 
	
	printf("No matches found, this is an acceptable matrix for gameplay. \n");

	[self printScoreMatrix:scoreMatrix];
	
	return FALSE;			
	
}

-(void) findRandomMatrix:(int)difficulty{
	
	//Matrix counter, just for curiousity's sake.
	int matrixCounter = 0;
	
	//Initial array made.
	[self buildMatrix:difficulty];
	
	//Checks the inital array, if a match is found, make a new array. Check and repeat.
	while ([self checkForMatches:matrix]) {
		matrixCounter ++;
		[self buildMatrix:difficulty];
	}
	matrixCounter ++;
	//printf("Matricies tested: %d \n", matrixCounter);	
}

-(void)setDifficultyLevel:(int)diff {
	difficultyLevel = diff;
	//printf("Set difficulty level to %d.\n", diff);
}

-(int)getDifficultyLevel{
	return difficultyLevel;
}


//Shift methods
-(void) shiftUp:(int)y{	
	int holder = matrix[y][0];
	for (int i = 0; i<ARRAYHEIGHT-1; i++) {  
		matrix[y][i] = matrix[y][i+1];
	}
	matrix[y][ARRAYHEIGHT-1] = holder;
}

-(void) shiftDown:(int)y{	
	int holder = matrix[y][ARRAYHEIGHT-1];
	for (int i = ARRAYHEIGHT-1; i>0; i--) {  
		matrix[y][i] = matrix[y][i-1];
	}
	matrix[y][0] = holder;
}

-(void) shiftLeft:(int)x{	
	int holder = matrix[0][x];
	for (int i = 0; i<ARRAYWIDTH-1; i++) {  
		matrix[i][x] = matrix[i+1][x];
	}
	matrix[ARRAYWIDTH-1][x] = holder;
}

-(void) shiftRight:(int)x{	
	int holder = matrix[ARRAYWIDTH-1][x];
	for (int i = ARRAYWIDTH-1; i>0; i--) {  
		matrix[i][x] = matrix[i-1][x];
	}
	matrix[0][x] = holder;
}

/**
 This method buildMatchMatrix will be called after a checkForMatches return of "TRUE" at this time.
 Later I may be able to building this into the checkForMatches function itself instead.
 Possible problems: we don't actually have to build a new matchMatrix when the checkForMatches method is
 called the times when its simply being used to find a random matrix.
 
 3.24.10 Added return value NSInteger to return number of matches found.
 **/
-(void) buildMatchMatrix:(int[ARRAYWIDTH][ARRAYHEIGHT])matrixWithMatch{
	printf("In the buildMatchMatrix method...\n");
	[self buildZeroesMatrix];
	
	//This code is the same as the checkForMatches method except the return values have been changed.
	//printf("This is the matrix to be checked:\n");
	//[Board printMatrix:matrixWithMatch];
	
	//HORIZONTAL CHECK
	//printf("Finding horizontal matches...");
	for (int i = 0; i<ARRAYWIDTH; i++) {  
		for (int j = 2; j<ARRAYHEIGHT; j++) {  
			if(matrixWithMatch[i][j] != Empty){
				if(matrixWithMatch[i][j] == matrixWithMatch[i][j-1]){
					if(matrixWithMatch[i][j] == matrixWithMatch[i][j-2]){
						//Setting the corresponding matchMatrix matched cells to 1
						matchMatrix[i][j-2] = 1;
						matchMatrix[i][j-1] = 1;
						matchMatrix[i][j] = 1;
					}
				}
			}  
		}
	}
	
	//VERTICAL CHECK
	//printf("Finding vertical matches...");
	for (int i = 0; i< ARRAYHEIGHT; i++) {  
		for (int j = 2; j<ARRAYWIDTH; j++) {  
			if(matrixWithMatch[j][i] != Empty){
				if(matrixWithMatch[j][i] == matrixWithMatch[j-1][i]){
					if(matrixWithMatch[j][i] == matrixWithMatch[j-2][i]){
						
						//Setting the corresponding matchMatrix matched cells to 1
						matchMatrix[j-2][i] = 1;
						matchMatrix[j-1][i] = 1;
						matchMatrix[j][i] = 1;
					}
				}		
			}
		}  
	} 
	
	//Printing the matchMatrix
	//[Board printMatrix:matchMatrix];
}

-(void) findMatchSpecifics:(int[ARRAYWIDTH][ARRAYHEIGHT]) _matchMatrix{
	
	NSInteger tilesPerMatch = 0;
	
	//HORIZONTAL CHECK
	for (int i = 0; i<ARRAYHEIGHT; i++) {  
		for (int j = 0; j<ARRAYWIDTH-2; j++) {  		
			while (_matchMatrix[j][i] == 1){
				tilesPerMatch ++;
				j++;
			} 
			if (tilesPerMatch >= 3){
				scoreMatrix[tilesPerMatch-3][1] = scoreMatrix[tilesPerMatch-3][1] + 1;
			}
			tilesPerMatch = 0;
		}  
	}
	
	//VERTICAL CHECK
	for (int i = 0; i<ARRAYWIDTH; i++) {  
		for (int j = 0; j<ARRAYHEIGHT-2; j++) {  	
			while (_matchMatrix[i][j] == 1){
				tilesPerMatch ++;
				j++;
			} 
			if (tilesPerMatch >= 3){
				scoreMatrix[tilesPerMatch-3][1] = scoreMatrix[tilesPerMatch-3][1] + 1;
			}
			tilesPerMatch = 0;
		}  
	}
	
	[self printScoreMatrix:scoreMatrix];
}

-(void) killMatchedTiles{
	printf("Killing matched tiles (back end)...");
	for (int i = 0; i<ARRAYHEIGHT; i++) {
		for (int j = 0; j<ARRAYWIDTH; j++) {  
			if(matchMatrix[j][i] == 1){
				
				//Kill the tile!
				matrix[j][i] = Empty;
			}
		}  
	} 
	[Board printMatrix:matrix];
}

//Back end "drop" gravity effect.
-(void) switchEmptyTiles {
	for (int i = ARRAYHEIGHT-1; i>0; i--) {
		for (int j = ARRAYWIDTH-1; j>=0; j--) {  
			
			if(matrix[j][i] == Empty){
				//printf("Found an empty tile at [%d][%d], shifting...\n", j, i);
				//Empy tile found, so shift everything above it down 1
				
				for (int k = i-1; k>=0; k--){
					//printf("Checking for non-empty tile at [%d][%d]...", j, k);
					//If everything above the empty tile is empty, no need to switch.
					if(matrix[j][k] != Empty){
						//printf("Found a non-empty tile at [%d][%d]: %d.\n", j, k, matrix[j][k]);
						
						//This code essentially switches the Empty tile with the next availible real
						//tile above it. It continues until the empty tile "bubbles" up to the top.
						//printf("Swapping %d to %d...\n", matrix[j][i], matrix[j][k]);
						matrix[j][i] = matrix[j][k];
						matrix[j][k] = Empty;
						break;
					}
					
				}
			}
			
		}
	}
	[Board printMatrix:matrix];
	/**
	printf("Moving ready to FALSE...\n");
	readyThing = FALSE;
	 **/
	//[self updateTilesFrom:matrix];
	//[self updateTilesView];
}

-(void) switchEmptyTiles:(int[ARRAYWIDTH][ARRAYHEIGHT])_matrix /**onColumn:(int)col**/ {
	//Check loop: starts at the bottom, looks for empty tiles
	//Shouldn't have to check the top row.
	for (int i = ARRAYHEIGHT-1; i>0; i--) {
		for (int j = ARRAYWIDTH-1; j>=0; j--) {  
			
			if(matrix[j][i] == Empty){
				//printf("Found an empty tile at [%d][%d], shifting...\n", j, i);
				//Empy tile found, so shift everything above it down 1
				
				for (int k = i-1; k>=0; k--){
					//printf("Checking for non-empty tile at [%d][%d]...", j, k);
					//If everything above the empty tile is empty, no need to switch.
					if(matrix[j][k] != Empty){
						//printf("Found a non-empty tile at [%d][%d]: %d.\n", j, k, matrix[j][k]);
						//This code essentially switches the Empty tile with the next availible real
						//tile above it. It continues until the empty tile "bubbles" up to the top.
						//printf("Swapping %d to %d...\n", matrix[j][i], matrix[j][k]);
						matrix[j][i] = matrix[j][k];
						matrix[j][k] = Empty;
						break;
					}
					
				}
			}
			
		}
	}
	//[Board printMatrix:matrix];
	
	[self updateTilesFrom:matrix];
	[self updateTilesView];
}

/**When calcWithScoreMatrix is implemented this will be OBSOLETE.
 -(NSInteger) calcScore:(NSInteger)numberOfMatches{
 NSInteger matches = numberOfMatches;
 
 //Exact scoring method needs to be spelled out here. This should work for now.
 
 if(matches<3)
 {
 matches = 3*matches;
 }
 else if(matches<6)
 {
 matches = 3*((matches-3)*2);
 }
 else if(matches<9)
 {
 matches = 3*((matches-6)*3);
 }
 else
 {
 matches = 3*((matches-9)*5);
 }
 return matches;
 }
 **/

//To be implemented, James Liu's request. Feel free to change the return value to void if needed.
-(int) calcScoreWithMatrix:(int[SCOREWIDTH][SCOREHEIGHT])_scoreMatrix{
	int matches = 0;
	for (int i = 0; i<9; i++)
	{
		matches = matches + scoreMatrix[i][1];
	}
	scoreMatrix[0][1] = scoreMatrix[0][1]*scoreMatrix[0][0]*1;
	scoreMatrix[1][1] = scoreMatrix[1][1]*scoreMatrix[1][0]*1;
	scoreMatrix[2][1] = scoreMatrix[2][1]*scoreMatrix[2][0]*2;
	scoreMatrix[3][1] = scoreMatrix[3][1]*scoreMatrix[3][0]*3;
	scoreMatrix[4][1] = scoreMatrix[4][1]*scoreMatrix[4][0]*3;
	scoreMatrix[5][1] = scoreMatrix[5][1]*scoreMatrix[5][0]*5;
	scoreMatrix[6][1] = scoreMatrix[6][1]*scoreMatrix[6][0]*5;
	scoreMatrix[7][1] = scoreMatrix[7][1]*scoreMatrix[7][0]*5;
	scoreMatrix[8][1] = scoreMatrix[8][1]*scoreMatrix[8][0]*5;
	
	int currentScore =0;
	for(int j=0; j<9; j++)
	{
		currentScore=currentScore+scoreMatrix[j][1];
	}
	if (matches > 6)
	{
		matches = currentScore *5;
	}
	else if (matches > 4)
	{
		matches = currentScore *3;
	}
	else if (matches > 2)
	{
		matches= currentScore *2;
	}
	else
	{
		matches = currentScore;
	}
	matches = matches * 10;
	[self buildScoreMatrix];
	return matches;
}

-(void)generateNewTiles{
	//printf("Generating new tiles...\n");
	//Randomize new tiles on the top row of the board (if possible)
	for (int i = 0; i < ARRAYWIDTH; i++){
		//printf("Checking tile [%d][%d]...", i, 0);
		
		if(matrix[i][0] == Empty){
			
			//printf("Found an empty tile, loading a new one...\n");
			
			matrix[i][0] = (arc4random() % [self getDifficultyLevel]);
		}
	}		 

}

-(void) handleTouchFrom:(CGPoint)begin to:(CGPoint)end{
	self.state = Animating;
	
	struct point_t _begin = [self viewToGridCoord: begin];
	struct point_t _end = [self viewToGridCoord: end];
	int index, dx, dy, disp;
	direction_t dir;
	
#ifdef DEBUG
	printf("Touch from x=%f, y=%f (col=%d,row=%d) \n\t to x=%f, y=%f (col=%d,row=%d)\n", begin.x, 
		   begin.y,  _begin.x, _begin.y, end.x, end.y, _end.x, _end.y);
#endif
	
	//dx = _begin.x - _end.x;
	//dy = _begin.y - _end.y;
	dx = begin.x - end.x;
	dy = begin.y - end.y;
	index = _begin.y*ARRAYWIDTH + _begin.x;
	dir = [self filterUsedDirections:[self getDirection:dx :dy]];
	
	switch (dir) {
		case North:
		case South:
			disp = abs(dy);
			break;
		case West:
		case East:
			disp = abs(dx);
			break;
		default:
			disp = 0;
			break;
	}
	
	
	///*
	if (dir != None) {
		// touch in one of the valid directions
		if (currentDirection == None) {
			// new touch, accept the direction
			currentDirection = dir;
		} 
		//currentDirection = dir;
		if ([self checkSameAxisForDirection:currentDirection andDirection:dir]) {
			// touch in the same direction
			[Sound soundEffect:0];	// click sound
			//[self animateTilesFrom:index withDisplacement:disp onDirection:dir];
			[self animateTilesFrom:index withOffset:disp onDirection:dir];
			[self updateTilesPosition];
			if (_begin.x != _end.x || _begin.y != _end.y) {
				//[self updateTilesFrom:index withDisplacement:1 onDirection:dir];
			}
			
			currentDirection = dir;
		}
	}
	
	//*/
	
	CGFloat timer = kInputLagDuration;
	[NSTimer scheduledTimerWithTimeInterval:timer target:self 
								   selector:@selector(readyForInput) 
								   userInfo:nil repeats:NO];
}

-(void) readyForInput{
	self.state = Idle;
}

-(void) loadTiles:(int[ARRAYWIDTH][ARRAYHEIGHT])_matrix {
	if (_matrix == nil) {
		return;	// ERROR RESPONSE NEEDED
	}
	
	struct point_t pos;
	
	for (int i = 0; i < ARRAYWIDTH; i++) {
		for (int j = 0; j < ARRAYHEIGHT; j++) {
			pos.x = i + 1;
			pos.y = j + 1;
			tiles[i + 1][j + 1] = [[Tile alloc] initWithPosition: pos andType:_matrix[i][j]];
		}
	}
	
	// row 0 and 12
	for (int i = 1; i <= ARRAYWIDTH; i++) {
		pos.x = i;
		pos.y = 0;
		tiles[i][0] = [[Tile alloc] initWithPosition: pos andType:_matrix[i- 1][ARRAYHEIGHT - 1]];
		pos.y = 12;
		tiles[i][12] = [[Tile alloc] initWithPosition: pos andType:_matrix[i -1][0]];
	}
	
	// col 0 and 9
	for (int j = 1; j <= ARRAYHEIGHT; j++) {
		pos.x = 0;
		pos.y = j;
		tiles[0][j] = [[Tile alloc] initWithPosition: pos andType:_matrix[ARRAYWIDTH - 1][j - 1]];
		pos.x = 9;
		tiles[9][j] = [[Tile alloc] initWithPosition: pos andType:_matrix[0][j - 1]];
	}
	
	// what about the corners??
	tiles[0][0]	 = nil;
	tiles[0][12] = nil;
	tiles[9][0]  = nil;
	tiles[9][12] = nil;
	
	[self checkTilesPositionConsistency];
	[self printTilesMatrix];
	// ready to load tiles to view
}

-(BOOL) checkTilesTypeConsistency:(int[ARRAYWIDTH][ARRAYHEIGHT]) expected {
	NSInteger inconsistencies = 0;
	for (int i = 0; i < ARRAYHEIGHT; i++) {
		for (int j = 0; j < ARRAYWIDTH; j++) {
			if (tiles[j + 1][i + 1].type != expected[j][i]) {
				inconsistencies++;
				printf("Type inconsistency #%d found at col:%d and row:%d between %d and %d(expected)!\n", 
					   inconsistencies, j, i, tiles[j + 1][i + 1].type, expected[j][i]);
			}
		}
	}
	
	return (inconsistencies == 0);
}


-(BOOL) checkTilesPositionConsistency {
	NSInteger inconsistencies = 0;
	struct point_t position;
	int positionCount[ARRAYWIDTH + 2][ARRAYHEIGHT + 2];
	
	for (int i = 0; i<ARRAYHEIGHT + 2; i++) {
		for (int j = 0; j<ARRAYWIDTH + 2; j++) {  
			positionCount[j][i] = 0;
		}  
	} 
	
	for (int i = 0; i < ARRAYHEIGHT + 2; i++) {
		for (int j = 0; j < ARRAYWIDTH + 2; j++) {
			
			if (tiles[j][i] == nil) {
				continue;
			}
			
			position = [tiles[j][i] viewToGridCoord];
			
			positionCount[position.x][position.y] += 1;
			
			if (j != position.x || i != position.y) {
				inconsistencies++;
				NSLog(@"Position inconsistency #%d found between board:(col:%d, row:%d) and view:(col:%d, row:%d)", 
					  inconsistencies, j, i, position.x, position.y);
			}
			
			
		}
	}
	
	printf("Position count on view:\n");
	for (int i = 0; i<ARRAYHEIGHT + 2; i++) {
		printf("%d:\t", i);
		for (int j = 0; j<ARRAYWIDTH + 2; j++) {  
			printf("%d ", positionCount[j][i]);  
		}  
		printf("\n");  
	} 
	
	return (inconsistencies == 0);
}

-(void) loadTilesToView:(UIView *)superview {
	for (int i = 0; i < ARRAYWIDTH + 2; i++) {
		for (int j = 0; j < ARRAYHEIGHT + 2; j++) {
			[superview addSubview: [tiles[i][j] containerView]];
			[superview sendSubviewToBack: [tiles[i][j] containerView]];
		}
	}
	// the board should be ready now!
	self.state = Idle;
}

-(void) animateTilesFrom:(int)index withDisplacement:(int)disp onDirection:(direction_t)dir {
	int cur, next, width, height;
	height = index / ARRAYWIDTH;
	width = index % ARRAYWIDTH;
	disp = 1;	// override for testing purposes
	[self checkTilesTypeConsistency:matrix];
	printf("row:%d\tcol:%d\tdisplacement:%d\n", height, width, disp);
	switch (dir) {
		case North:
			for (int i = 0;  i < ARRAYHEIGHT;  i++) {
				cur = (height - i + ARRAYHEIGHT) % ARRAYHEIGHT;
				next = (cur + disp + ARRAYHEIGHT) % ARRAYHEIGHT;
				printf("cur:%d\tnext:%d\n", cur, next);
				[tiles[width][cur] animateFlipToTile: tiles[width][next].type onDirection:North];
			}
			break;
		case South:
			for (int i = 0;  i < ARRAYHEIGHT;  i++) {
				cur = (height + i) % ARRAYHEIGHT;
				next = (cur - disp + ARRAYHEIGHT) % ARRAYHEIGHT;
				[tiles[width][cur] animateFlipToTile: tiles[width][next].type onDirection:South];
			}
			break;
		case East:
			for (int i = 0;  i < ARRAYWIDTH;  i++) {
				cur = (width + i) % ARRAYWIDTH;
				next = (cur - disp + ARRAYWIDTH) % ARRAYWIDTH;
				[tiles[cur][height] animateFlipToTile: tiles[next][height].type onDirection:East];
			}
			break;
		case West:
			for (int i = 0;  i < ARRAYWIDTH;  i++) {
				cur = (width - i + ARRAYWIDTH) % ARRAYWIDTH;
				next = (cur + disp + ARRAYWIDTH) % ARRAYWIDTH;
				[tiles[cur][height] animateFlipToTile: tiles[next][height].type onDirection:West];
			}
			break;
		default:
			break;
	}
}

-(void) animateTilesFrom:(int) index withOffset:(int) offset onDirection:(direction_t) dir{
	int height = index / ARRAYWIDTH;
	int width = index % ARRAYWIDTH;
	
	switch (dir) {
		case North:
		case South:
			for (int i = 0;  i < ARRAYHEIGHT + 2;  i++) {
				[tiles[width + 1][i] shiftWithOffset:offset onDirection:dir];
			}
			break;
		case East:
		case West:
			for (int i = 0;  i < ARRAYWIDTH + 2;  i++) {
				[tiles[i][height + 1] shiftWithOffset:offset onDirection:dir];
			}
			break;
		default:
			printf("Board::animateTilesFrom: Warning, invalid direction!\n");
			break;
	}
	
}

-(void) animateTilesFall {
	self.state = Animating;
	int active, firstFall = 0;
	
	NSLog(@"Before");
	//[Board printMatrix:matrix];
	do {
		active = 0;
		for (int i = ARRAYHEIGHT - 1; i  > 0; i--) {
			for (int j = 0; j < ARRAYWIDTH; j++) {
				if (matrix[j][i] == Empty) {
					if (matrix[j][i - 1] == Empty) {
						continue;
					} else {
						active++;
						firstFall++;
					}
					
					// visual fall
					[tiles[j + 1][i + 1] moveUp:YES];
					[tiles[j + 1][i] moveDown:YES];
					
					Tile * tempTile = tiles[j + 1][i + 1];
					tiles[j + 1][i + 1] = tiles[j + 1][i];
					tiles[j + 1][i] = tempTile;
					
					// logical fall
					matrix[j][i] = matrix[j][i - 1];
					matrix[j][i - 1] = Empty;
					if (firstFall == 1) {
						[Sound soundEffect:9];
					}
					
				}
			}
		}
		//NSLog(@"After");
		//[Board printMatrix:matrix];
		//active--;
		
	} while (active > 0);
	
	self.state = Idle;
}

-(void) animateAtomicTilesFall:(NSTimer *) timer {
	[self updateTilesFrom:matrix];
	//[self updateTilesView];	// update already does it 
	[Sound soundEffect:9];
	printf("Moving ready to FALSE...\n");
	//readyThing = FALSE;
}

-(void) updateTilesView {
	printf("inside updateTilesView...\n");
	for (int i = 0; i < ARRAYHEIGHT; i++) {
		for (int j = 0; j < ARRAYWIDTH; j++) {
			[tiles[j][i] updateImage];
		}
	}
}

-(void) updateTilesPosition{
	// adjust the tiles position in the view
	//[self adjustTilesPosition];
	
	// adjust the tiles position in the board
	int numSwaps = 1;
	struct point_t point;
	struct point_t pointComp;
	
	// adjust the tiles seen in the board
	do {
		numSwaps = 0;
		
		for (int i = 0; i < ARRAYHEIGHT + 2; i++) {
			for (int j = 0; j < ARRAYWIDTH + 2; j++) {
				if (tiles[j][i] == nil) {
					//NSLog(@"Board::updateTilesPosition: <<WARNING>> Null tile at col:%d\trow:%d", j, i);
					continue;
				}
				
				point = [tiles[j][i] viewToGridCoord];
				if (point.x == j && point.y == i) {
					// tile already in place
					//NSLog(@"Board::updateTilesPosition: Alreay in place");
				} else {
					// tile not in place, need to move
					pointComp = [tiles[point.x][point.y] viewToGridCoord];
					
					if (point.x == pointComp.x && point.y == pointComp.y) {
						//numSwaps++;	// work harder, an attempt to fix this condition
						//NSLog(@"Board::updateTilesPosition: <<WARNING>> collision for col:%d\trow:%d", point.x, point.y);
					} else {
						// swap
						numSwaps++;
						//NSLog(@"Board::updateTilesPosition: Swap time");
						Tile * tempTile = tiles[point.x][point.y];
						tiles[point.x][point.y] = tiles[j][i];
						tiles[j][i] = tempTile;
						NSLog(@"Board::updateTilesPosition: swaping (%d,%d) and (%d, %d)", j, i, point.x, point.y);
					}
				}
				
			}
		}
		
	} while (numSwaps > 0);
	
	BOOL animateThenUpdate = kAnimateThenUpdate;
	if(animateThenUpdate)
	{
		// override matrix with values on view
		for (int i = 0; i < ARRAYHEIGHT; i++) {
			for (int j = 0; j < ARRAYWIDTH; j++) {
				matrix[j][i] = tiles[j + 1][i + 1].type;
			}
		}
	}
	
	[self checkTilesTypeConsistency:matrix];
	
	// adjust the corners
	// row 0 from 11 (10) and 12 from 1 (0)
	for (int j = 1; j <= ARRAYWIDTH; j++) {
		[tiles[j][0]					switchToType: matrix[j - 1][ARRAYHEIGHT - 1]];
		[tiles[j][ARRAYHEIGHT + 1]		switchToType: matrix[j - 1][0]];
	}
	
	// col 0 from 8 (7) and 9 from 1 (0)
	for (int i = 1; i <= ARRAYHEIGHT; i++) {
		[tiles[0][i]					switchToType: matrix[ARRAYWIDTH - 1][i - 1]];
		[tiles[ARRAYWIDTH + 1][i]		switchToType: matrix[0][i - 1]];
	}
	
	//[self checkTilesPositionConsistency];
	
	[self printTilesMatrix];
}


-(void) adjustTilesPosition {
	//NSLog (@"Board::adjustTilesPosition: Before adjusting");
	//[self checkTilesPositionConsistency];
	
	for (int i = 0; i < ARRAYHEIGHT + 2; i++) {
		for (int j = 0; j < ARRAYWIDTH + 2; j++) {
			[tiles[j][i] adjustPosition];
		}
	}
	
	//NSLog (@"Board::adjustTilesPosition: After adjusting");
	//[self checkTilesPositionConsistency];
	
}

-(void) updateTilesFrom: (int) index withDisplacement:(int) disp onDirection: (direction_t) dir {
	int width, height;
	height = index / ARRAYWIDTH;
	width = index % ARRAYWIDTH;
	
	disp = 1;	// override for testing purposes
	printf("\nThe matrix before motion:");
	[Board printMatrix:matrix];
	
	//[self checkTilesConsistency:matrix];
	
	switch (dir) {
		case North:
			for (int i = 0; i < disp; i++) {
				[self shiftUp:width];
			}
			break;
		case South:
			for (int i = 0; i < disp; i++) {
				[self shiftDown:width];
			}			
			break;
		case East:
			for (int i = 0; i < disp; i++) {
				[self shiftRight:height];
			}
			break;
		case West:
			for (int i = 0; i < disp; i++) {
				[self shiftLeft:height];
			}
			break;
		default:
			break;
	}
	
	/*
	 for (int i = 0; i < ARRAYHEIGHT; i++) {
	 for (int j = 0; j < ARRAYWIDTH; j++) {
	 tiles[j][i].type = matrix[j][i];
	 }
	 }
	 */
	
	//[self checkTilesConsistency:matrix];
	
}

-(void) updateTilesFrom:(int[ARRAYWIDTH][ARRAYHEIGHT]) uptodate {
	printf("Inside uptateTilesFrom 2...\n");
	for (int i = 0; i < ARRAYHEIGHT; i++) {
		for (int j = 0; j < ARRAYWIDTH; j++) {
			[tiles[j + 1][i + 1]	switchToType: uptodate[j][i]];
		}
	}

	// adjust the corners
	// row 0 from 11 (10) and 12 from 1 (0)
	for (int j = 1; j <= ARRAYWIDTH; j++) {
		[tiles[j][0]					switchToType: uptodate[j - 1][ARRAYHEIGHT - 1]];
		[tiles[j][ARRAYHEIGHT + 1]		switchToType: uptodate[j - 1][0]];
	}
	
	// col 0 from 8 (7) and 9 from 1 (0)
	for (int i = 1; i <= ARRAYHEIGHT; i++) {
		[tiles[0][i]					switchToType: uptodate[ARRAYWIDTH - 1][i - 1]];
		[tiles[ARRAYWIDTH + 1][i]		switchToType: uptodate[0][i - 1]];
	}
}

// determine the direction based on x and y displacements
// assuming inverted y-axis system
-(direction_t) getDirection:(int)dx :(int)dy {
	if (dx == 0) {
		if (dy == 0) {
			return None;
		} else if (dy > 0) {
			return North;
		} else {
			return South;
		}
	} else if (dx > 0) {
		if (dy == 0) {
			return West;
		} else if (dy > 0) {
			return NorthWest;
		} else {
			return SouthWest;
		}
	} else {
		if (dy == 0) {
			return East;
		} else if (dy > 0) {
			return NorthEast;
		} else {
			return SouthEast;
		}
	}
	
}

// We just use N, S, E, and W
// filter out NE, NW, SE and SW
-(direction_t) filterUsedDirections:(direction_t)raw {
	switch (raw) {
		case North:
		case South:
		case West:
		case East:
			return raw;
		default:
			return None;
	}
}

-(BOOL) checkSameAxisForDirection:(direction_t) aDir andDirection:(direction_t)anotherDir {
	switch (aDir) {
		case North:
		case South:
			return (anotherDir == South || anotherDir == North);
		case East:
		case West:
			return (anotherDir == East || anotherDir == West);
		default:
			return NO;
			break;
	}
}

-(void) resetCurDirection {
	currentDirection = None;
}

-(NSInteger) matchTiles{
	NSInteger timer = kFallCycleDuration;
	if ([self checkForMatches:matrix] == TRUE ) {
		printf("loop!\n");
		
		//This is the "back-end" tile-killer and will remake the matrix 
		//=======
		[self buildMatchMatrix:matrix];
		[self findMatchSpecifics:matchMatrix];
		[self killMatchedTiles];
		
		printf("Poofing matched tiles...\n");
		for (int i = 0; i < ARRAYHEIGHT; i++) {
			for (int j = 0; j < ARRAYWIDTH; j++) {
				if(matchMatrix[j][i] == 1){
					[tiles[j + 1][i + 1] animatePoofWithDelay:0.0];
				}
			}
		}
		
		[Sound soundEffect:1];	// single poof sound
		
		[NSTimer scheduledTimerWithTimeInterval:timer target:self 
										selector:@selector(animateTilesFall) 
										userInfo:nil repeats:NO];	
	}
	
	/** If there's no matches in the first place, the while loop won't run. 
	 The moveScore should be 0, meaning no matches, no changes to the board,
	 and no poofing or changes to the board on the front end.
	 **/
	
	score = [self calcScoreWithMatrix:scoreMatrix];
	printf("Score returning:%d \n", score);
	
	return score;
}


-(NSInteger) matchTilesFast{
	//NSInteger timer = kFallCycleDuration;
	if ([self checkForMatches:matrix]) {
		printf("loop!\n");
		
		//This is the "back-end" tile-killer and will remake the matrix 
		//=======
		[self buildMatchMatrix:matrix];
		[self findMatchSpecifics:matchMatrix];
		[self killMatchedTiles];
		
		printf("Poofing matched tiles...\n");
		for (int i = 0; i < ARRAYHEIGHT; i++) {
			for (int j = 0; j < ARRAYWIDTH; j++) {
				if(matchMatrix[j][i] == 1){
					[tiles[j + 1][i + 1] animatePoofWithDelay:0.0];
				}
			}
		}
		[Sound soundEffect:1];
		
		[self switchEmptyTiles];
		[NSTimer scheduledTimerWithTimeInterval:0.7f target:self 
										selector:@selector(animateAtomicTilesFall:) 
										userInfo:nil repeats:NO];
		 
		//*
		 [NSTimer scheduledTimerWithTimeInterval:1.0f target:self 
										selector:@selector(matchTiles) 
										userInfo:nil repeats:NO];	
		 //*/
	}
	
	/** If there's no matches in the first place, the while loop won't run. 
	 The moveScore should be 0, meaning no matches, no changes to the board,
	 and no poofing or changes to the board on the front end.
	 **/
	
	score = [self calcScoreWithMatrix:scoreMatrix];
	printf("Score returning:%d \n", score);
	
	return score;
}


-(BOOL) checkForEmptyBoard:(int[ARRAYWIDTH][ARRAYHEIGHT]) checkMatrix{
	for (int i = ARRAYHEIGHT - 1; i  >= 0; i--) {
		for (int j = 0; j < ARRAYWIDTH; j++) {
			if(checkMatrix[j][i] != Empty){
				return NO;	// return on first non-empty tile found
			}
		}
	}
	
	return YES;
}

-(BOOL) isEmpty {
	return [self checkForEmptyBoard:matrix];
}

-(struct point_t) viewToGridCoord: (CGPoint) coord{
	CGRect rect = kStdTileFrame;
	struct point_t point;
	
	point.x = (int)(coord.x/rect.size.width);
	point.y = (int)(coord.y/rect.size.height) - 1;
	
	int x = (int)(abs(coord.x - rect.origin.x)/rect.size.width);
	int y = (int)(abs(coord.y - rect.origin.y)/rect.size.height);
	
	if (point.x != x || point.y != y) {
		NSLog(@"Board::viewToGridCoord <<Equation 1>> x:%d, y:%d",point.x, point.y);
		NSLog(@"Board::viewToGridCoord <<Equation 2>> x:%d, y:%d",x, y);
		
	}
	return point; 
}

-(CGPoint) gridToViewCoord: (struct point_t) coord{
	CGRect rect = kStdTileFrame;
	
	CGPoint point = CGPointMake((coord.x + 0.5)* rect.size.width + rect.origin.x, 
								(coord.y + 0.5)* rect.size.height + rect.origin.y);
	return point;
}

+(void) printMatrix:(int[ARRAYWIDTH][ARRAYHEIGHT]) _matrix{
	
	printf("This is the matrix:\n");
	for (int i = 0; i<ARRAYHEIGHT; i++) {
		printf("%d:\t", i);
		for (int j = 0; j<ARRAYWIDTH; j++) {  
			printf("%d ", _matrix[j][i]);  
		}  
		printf("\n");  
	}
}

-(void) printScoreMatrix:(int[9][2]) _matrix{
	printf("This is the scoreMatrix:\n");
	for (int i = 0; i<2; i++) {
		printf("%d:\t", i);
		for (int j = 0; j<9; j++) {  
			printf("%d ", _matrix[j][i]);  
		}  
		printf("\n");  
	}
}

-(void) printTilesMatrix {
	
	[Board printMatrix:matrix];
	
	printf("This is the matrix of tiles:\n");
	
	
	for (int i = 0; i<ARRAYHEIGHT + 2; i++) {
		printf("%d:\t", i);
		for (int j = 0; j<ARRAYWIDTH + 2; j++) {  
			printf("%d ", tiles[j][i].type);  
		}  
		printf("\n");  
	} 
}


-(void) dealloc {
	for (int i = 0; i<ARRAYHEIGHT; i++){
		for (int j = 0; j<ARRAYWIDTH; j++){
			[tiles[j][i] release];
		}
	}
	[super dealloc];
}

-(void) loadGameFromFile:(NSString *)filename {
	NSString * path = [[NSBundle mainBundle] bundlePath];
	NSString * file = [path stringByAppendingPathComponent:filename];
	
	Matrix * game = [[Matrix alloc] initWithContentsOfFile:file rowSize:(ARRAYHEIGHT + 1) colSize:ARRAYWIDTH];
	NSLog(@"Board:\tgame: %@", [game description]);
	
	
	NSString * item = [game objectAtRow:0 andCol:0];
	difficultyLevel = [item integerValue]; 
	item = [game objectAtRow:0 andCol:1];
	score = [item integerValue]; 
	item = [game objectAtRow:0 andCol:2];
	time = [item integerValue]; 
	
	for (int i = 0; i < ARRAYHEIGHT; i++) {
		for (int j = 0; j < ARRAYWIDTH; j++) { 
			item = [game objectAtRow:(i + 1) andCol:j];
			matrix[j][i] = [item intValue];
		}  
	}
	
	[game release];
}

-(BOOL) saveGame {
	//Matrix saving = - (id)initWithRowSize: (NSUInteger) rows colSize:(NSUInteger) cols;
	NSString * path = [[NSBundle mainBundle] bundlePath];
	NSString * file = [path stringByAppendingPathComponent:@"saving.plist"];
	
	Matrix * saving = [[Matrix alloc] initWithRowSize:(ARRAYHEIGHT + 1) colSize:ARRAYWIDTH];
	// save difficulty
	[saving insertObject:[NSString stringWithFormat:@"%d", difficultyLevel] atRow:0 andCol:0];
	// save score
	[saving insertObject:[NSString stringWithFormat:@"%d", score] atRow:0 andCol:1];
	// save time
	[saving insertObject:[NSString stringWithFormat:@"%d", time] atRow:0 andCol:2];
	// fillers
	for (int j = 3; j < ARRAYWIDTH; j++) {
		[saving insertObject:[NSString stringWithFormat:@"%d", 0] atRow:0  andCol:j];	
	}
	// save the board
	for (int i = 0; i < ARRAYHEIGHT; i++) {
		for (int j = 0; j < ARRAYWIDTH; j++) {
			//printf("row:%d\tcol:%d", i, j);
			[saving insertObject:[NSString stringWithFormat:@"%d", matrix[j][i]] atRow:(i + 1)  andCol:j];
		}  
		//printf("\n");
	}
	NSLog(@"Board:\tgame: %@", [saving description]);
	
	return	[saving writeToFile:file atomically:YES];
}

@end
